/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor;
import java.util.ArrayList;
import javax.swing.text.BadLocationException;
import javax.swing.text.AbstractDocument;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.event.DocumentEvent;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.CannotRedoException;
/**
* Document operations. This class enhances basic
* marks operations by interaction with the data of the document.
* All mark operations should go through this class.
* This class implements <tt>AbstractDocument.Content</tt> interface.
* BOL means Begin of Line position.
* EOL means End of Line position.
*
* @author Miloslav Metelka
* @version 1.00
*/
class DocOp implements AbstractDocument.Content {
/** Number of line cache entries */
private static final int CACHE_LEN = 5;
private static final String WRONG_POSITION = "Wrong position "; // NOI18N
private static final String DOC_LEN = ". Document length is "; // NOI18N
/** Default mark distance for inserting to the document.
* If the insert is made then the distance between nearest
* marks around insertion point is checked and if it's greater
* than the max mark distance then another mark(s) are inserted.
*/
private int MARK_DISTANCE;
/** Maximum mark distance. When there is an insertion done in document
* and the distance between marks get greater than this value, another
* mark will be inserted.
*/
private int MAX_MARK_DISTANCE;
/** Minimum mark distance for removals. When there is a removal done
* in document and it makes the marks to get closer than this value, then
* the second mark will be removed.
*/
private int MIN_MARK_DISTANCE;
/** Size of one batch for updating syntax marks */
private int SYNTAX_UPDATE_BATCH_SIZE;
/** Document cache */
private DocCache cache;
/** Document cache support */
private DocCacheSupport cacheSupport;
/** Document marks handling */
private DocMarks marks;
/** Document */
private BaseDocument doc;
/** Document len */
private int docLen;
/** End of current line forward finder */
private FinderFactory.EOLFwdFinder eolFwdFinder;
/** Current line begining backward finder */
private FinderFactory.BOLBwdFinder bolBwdFinder;
/** Find both BOL and EOL one or more lines forward in text */
private FinderFactory.BEOLLineFwdFinder beolLineFwdFinder;
/** Find both BOL and EOL when position is known */
private FinderFactory.BEOLPosFwdFinder beolPosFwdFinder;
/** Finder for visual x-coord to position conversion */
private FinderFactory.VisColPosFwdFinder visColPosFwdFinder;
/** Finder for position to x-coord conversion */
private FinderFactory.PosVisColFwdFinder posVisColFwdFinder;
/** Line and column information cache */
private CacheEntry lineCache[];
/** End of document mark. This mark should speed up getting the line number
* of the end of document so that the cache fragment doesn't have to move.
*/
private Mark endMark;
/** Mark that is inserted at the end of line (better said after the end
* of line) where the insertion currently
* occurs. The mark is possibly moved and/or updated before
* the insertion/removal occurs. Then after the insertion/removal is done
* mark syntax state is checked again. If it's the same as before it's not
* necessary to paint the next line after insertion. If it's not the same
* the painting must continue till the syntax mark with matching state.
*/
MarkFactory.SyntaxMark eolMark;
/** Renderer for doing syntax mark updates after insertion/removal. */
private SyntaxUpdateRenderer suRenderer;
/** First syntax mark that is laying in the left direction
* from the update point.
*/
private MarkFactory.SyntaxMark leftUpdateMark;
/** Array holding returned line number */
private int[] tmpLine = new int[1];
/** Statistics */
int statCacheHit;
int statCacheMiss;
/** Construct new document marks operations.
* Since this class uses both cache and marks
* both these classes must be already created in document.
*/
DocOp() {
cacheSupport = new MemCacheSupport();
cache = new DocCache(cacheSupport, 2048, true); // !!!
marks = new DocMarks();
// initialize cache
lineCache = new CacheEntry[CACHE_LEN];
for (int i = 0; i < CACHE_LEN; i++) {
lineCache[i] = new CacheEntry();
}
// create necessary finders
bolBwdFinder = new FinderFactory.BOLBwdFinder();
eolFwdFinder = new FinderFactory.EOLFwdFinder();
beolLineFwdFinder = new FinderFactory.BEOLLineFwdFinder();
beolPosFwdFinder = new FinderFactory.BEOLPosFwdFinder();
visColPosFwdFinder = new FinderFactory.VisColPosFwdFinder();
posVisColFwdFinder = new FinderFactory.PosVisColFwdFinder();
// init endMark and eolMark
try {
endMark = insertMark(docLen, false);
eolMark = new MarkFactory.SyntaxMark() {
protected void removeUpdateAction(int pos, int len) {
// prevent default removing of this mark
}
};
insertMark(eolMark, docLen);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
// create shared syntax update renderer
suRenderer = new SyntaxUpdateRenderer();
}
void setDocument(BaseDocument doc) {
this.doc = doc;
MARK_DISTANCE = ((Integer)doc.getProperty(
Settings.MARK_DISTANCE)).intValue();
MAX_MARK_DISTANCE = ((Integer)doc.getProperty(
Settings.MAX_MARK_DISTANCE)).intValue();
MIN_MARK_DISTANCE = ((Integer)doc.getProperty(
Settings.MIN_MARK_DISTANCE)).intValue();
SYNTAX_UPDATE_BATCH_SIZE = ((Integer)doc.getProperty(
Settings.SYNTAX_UPDATE_BATCH_SIZE)).intValue();
}
public synchronized Position createPosition(int offset) throws BadLocationException {
boolean insertAfter = (offset == 0); // support AbstractDocument keep marks at position 0 behavior
if (offset == docLen + 1) { // support AbstractDocument's content initial "\n" behavior
offset = docLen;
}
return new BasePosition(this, offset,
insertAfter ? Position.Bias.Backward : Position.Bias.Forward);
}
public synchronized Position createPosition(int offset, Position.Bias bias)
throws BadLocationException {
return new BasePosition(this, offset, bias);
}
public synchronized int length() {
return docLen;
}
public String getString(int where, int len) throws BadLocationException {
return new String(getChars(where, len));
}
public void getChars(int where, int len, Segment txt)
throws BadLocationException {
txt.array = getChars(where, len);
txt.offset = 0;
txt.count = len;
}
/** Retrieve the characters from the document cache. */
synchronized char[] getChars(int pos, int len) throws BadLocationException {
return cache.read(pos, len, null); // no cache fragment optimization yet
}
/** Get the characters into the given buffer. The buffer must contain
* enough space for the requested data.
*/
synchronized void getChars(int pos, char ret[], int offset, int len)
throws BadLocationException {
cache.read(pos, ret, offset, len, null);
}
public UndoableEdit insertString(int offset, String text) throws BadLocationException {
ModifyUndoEdit undoEdit = new ModifyUndoEdit(false, offset, text);
insertEdit(undoEdit);
return undoEdit;
}
public UndoableEdit insert(int offset, char[] chars) throws BadLocationException {
ModifyUndoEdit undoEdit = new ModifyUndoEdit(false, offset, chars);
insertEdit(undoEdit);
return undoEdit;
}
synchronized void insertEdit(ModifyUndoEdit undoEdit) throws BadLocationException {
int offset = undoEdit.getOffset();
checkEOLMark(offset);
if (undoEdit.isTextValid()) {
cache.insertString(offset, undoEdit.getText(), null);
} else { // chars buffer valid
cache.insert(offset, undoEdit.getChars(), null);
}
insertUpdate(undoEdit); // always done to update line cache
}
public UndoableEdit remove(int offset, int len) throws BadLocationException {
ModifyUndoEdit undoEdit = new ModifyUndoEdit(true, offset, getChars(offset, len));
removeEdit(undoEdit);
return undoEdit;
}
synchronized void removeEdit(ModifyUndoEdit undoEdit) throws BadLocationException {
checkEOLMark(undoEdit.getOffset());
cache.remove(undoEdit.getOffset(), undoEdit.getLength(), null); // no cache fragment optimization yet
removeUpdate(undoEdit);
}
synchronized int find(Finder finder, int startPos, int limitPos) throws BadLocationException {
return cache.find(finder, startPos, limitPos, null);
}
/** Insert new mark at specified position. This function
* finds the appropriate line number.
*/
synchronized void insertMark(Mark mark, int pos)
throws BadLocationException, InvalidMarkException {
if (pos < 0 || pos > docLen) {
throw new BadLocationException(WRONG_POSITION + pos + DOC_LEN + docLen, pos);
}
marks.insertMark(mark, pos, getLineImpl(pos));
}
/** Insert mark when knowing even the line offset.
* This method is used solely by <tt>Analyzer</tt> when
* document is read.
*/
void insertMark(Mark mark, int pos, int line)
throws BadLocationException, InvalidMarkException {
marks.insertMark(mark, pos, line);
}
/** Write directly to the cache-support.
* This method is used solely by <tt>Analyzer</tt> when
* document is read.
*/
void directCacheWrite(int pos, char cache[], int offset, int len)
throws BadLocationException {
cacheSupport.write(pos, cache, offset, len);
}
/** Initialize the default fragment of the cache by the given data.
* This method is used solely by <tt>Analyzer</tt> when
* document is read.
*/
void initCacheContent(char initCache[], int offset, int cacheLen) {
cache.initCacheContent(initCache, offset, cacheLen);
}
/** Insert new mark at specified position. The function
* finds appropriate line number.
*/
Mark insertMark(int pos, boolean insertAfter)
throws BadLocationException {
Mark mark = new Mark(insertAfter);
try {
insertMark(mark, pos);
} catch(InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
return mark;
}
/** Moves the mark to different position */
synchronized void moveMark(Mark mark, int newPos)
throws BadLocationException, InvalidMarkException {
if (newPos < 0 || newPos > docLen) {
throw new BadLocationException(WRONG_POSITION + newPos + DOC_LEN + docLen, newPos);
}
mark.remove();
marks.insertMark(mark, newPos, getLineImpl(newPos));
}
synchronized Mark getOffsetMark(int pos, Class markClass) {
return marks.getOffsetMark(pos, markClass);
}
synchronized void renderMarks(DocMarks.Renderer r) {
marks.render(r);
}
/** Get begin of line from position on that line
* @param position where the search begins
* @return position of begin of the same line
*/
synchronized int getBOL(int pos) throws BadLocationException {
return getBOLImpl(pos);
}
/** Is the position at the begining of the line? */
synchronized boolean isBOL(int pos) throws BadLocationException {
return (pos == getBOLImpl(pos));
}
/** Get end of line position from position on that line. */
synchronized int getEOL(int pos) throws BadLocationException {
return getEOLImpl(pos);
}
/** Get end of line after the new-line position from position on that line.
* This is in fact the begining of the next line except the last line.
*/
synchronized int getEOLNL(int pos) throws BadLocationException {
int eol = getEOLImpl(pos);
if (eol < docLen) {
return eol++;
}
return eol;
}
/** Is the position at the end of the line? */
synchronized boolean isEOL(int pos) throws BadLocationException {
return (pos == getEOLImpl(pos));
}
/** Get the begining position of specified line
* @param line line offset for which the BOL should be determined
* @return position of the begining of line or -1 if line is invalid
*/
synchronized int getBOLFromLine(int line) {
return getBOLFromLineImpl(line);
}
/** Get end of line position from specified line */
synchronized int getEOLFromLine(int line) {
int pos = getBOLFromLineImpl(line);
if (pos < 0) {
return 0;
}
try {
return getEOLImpl(pos);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
return -1;
}
}
/** Advance given position n-lines forward/backward and return BOL. */
synchronized int getBOLRelLine(int pos, int relLine)
throws BadLocationException {
int line = getLineImpl(pos);
line += relLine;
return getBOLFromLineImpl(line);
}
/** Advance given position n-lines forward/backward and return BOL. */
synchronized int getEOLRelLine(int pos, int relLine)
throws BadLocationException {
int line = getLineImpl(pos);
line += relLine;
pos = getBOLFromLineImpl(line);
if (pos < 0) {
return pos;
}
try {
return getEOLImpl(pos);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
return -1;
}
}
/** Get line from position. This is used for example when removal
* from document is made to find right line number for marks update.
*/
synchronized int getLine(int pos) throws BadLocationException {
return getLineImpl(pos);
}
synchronized int getLineCount() {
int lineCnt;
try {
lineCnt = endMark.getLine() + 1;
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
return 0;
}
return lineCnt;
}
/** Get position on line from visual column. This method can be used
* only for superfixed font i.e. all characters of all font styles
* have the same width.
* @param visCol visual column
* @param startLinePos position of line start
* @return position on line for particular x-coord
*/
synchronized int getOffsetFromVisCol(int visCol, int startLinePos)
throws BadLocationException {
if (visCol <= 0) {
return startLinePos;
}
visColPosFwdFinder.setVisCol(visCol);
visColPosFwdFinder.setTabSize(doc.getTabSize());
int pos = cache.find(visColPosFwdFinder, startLinePos, -1, null);
return (pos != -1) ? pos : getEOLImpl(startLinePos);
}
/** Get visual column from position. This method can be used
* only for superfixed font i.e. all characters of all font styles
* have the same width.
* @param pos position for which the visual column should be returned
* the function itself computes the begining of the line first
*/
synchronized int getVisColFromPos(int pos) throws BadLocationException {
int startLinePos = getBOLImpl(pos);
posVisColFwdFinder.setTabSize(doc.getTabSize());
cache.find(posVisColFwdFinder, startLinePos, pos, null);
return posVisColFwdFinder.getVisCol();
}
private int getBOLImpl(int pos) throws BadLocationException {
if (pos <= 0) { // must be first line
if (pos == 0) {
return 0;
}
throw new BadLocationException(WRONG_POSITION + pos + DOC_LEN + length(), pos);
}
// search cache
for (int i = 0; i < CACHE_LEN; i++) {
if (pos >= lineCache[i].bol && pos <= lineCache[i].eol) {
// if (lineCache[i].bol != getCheckBOL(pos)) { // !!! remove
// checkCache();
// }
cacheMoveFirst(i);
statCacheHit++;
return lineCache[0].bol;
}
}
statCacheMiss++;
// search document when not found
return cache.find(bolBwdFinder, pos, 0, null) + 1;
}
private int getEOLImpl(int pos) throws BadLocationException {
if (pos < 0) {
throw new BadLocationException(WRONG_POSITION + pos + DOC_LEN + length(), pos);
}
// search cache
for (int i = 0; i < CACHE_LEN; i++) {
if (pos >= lineCache[i].bol && pos <= lineCache[i].eol) {
// if (lineCache[i].eol != getCheckEOL(pos)) {
// checkCache();
// }
cacheMoveFirst(i);
statCacheHit++;
return lineCache[0].eol;
}
}
statCacheMiss++;
// search document
pos = cache.find(eolFwdFinder, pos, -1, null);
return (pos != -1) ? pos : docLen;
}
private int getBOLFromLineImpl(int line) {
if (line < 0) {
return -1;
}
// check cache first
for (int i = 0; i < CACHE_LEN; i++) {
if (line == lineCache[i].line) {
// if (lineCache[i].bol != getCheckBOLFromLine(line)) { // !!! remove
// checkCache();
// }
cacheMoveFirst(i);
statCacheHit++;
return lineCache[0].bol;
}
}
statCacheMiss++;
// load line into cache
cacheMoveFirst(CACHE_LEN - 1);
return cacheLoadLine(line); // returns -1 for wrong pos
}
private int getLineImpl(int pos) throws BadLocationException {
if (pos < 0 || pos > docLen) {
throw new BadLocationException(WRONG_POSITION + pos + DOC_LEN + length(), pos);
}
// search cache
for (int i = 0; i < CACHE_LEN; i++) {
if (pos >= lineCache[i].bol && pos <= lineCache[i].eol) {
// if (lineCache[i].line != getCheckLine(pos)) {
// checkCache();
// }
cacheMoveFirst(i);
statCacheHit++;
return lineCache[0].line;
}
}
statCacheMiss++;
// load line into cache
cacheMoveFirst(CACHE_LEN - 1);
return cacheLoadLineByPos(pos);
}
/** If this position is at BOL leave it as it is if returnIfBOL is set.
* Otherwise get BOL of the next line or end of document.
* @param returnIfBOL return immediately if the position is already on BOL
*/
private int adjustNextBOL(int pos, boolean returnIfBOL) throws BadLocationException {
if (returnIfBOL && pos == getBOLImpl(pos)) {
return pos;
}
pos = getEOLImpl(pos);
if (pos < docLen) {
pos++;
}
return pos;
}
/** Get the first syntax mark that is in the left direction from the desired
* position.
*/
MarkFactory.SyntaxMark getLeftSyntaxMark(int pos) {
return (MarkFactory.SyntaxMark)marks.getLeftMark(pos, MarkFactory.SyntaxMark.class);
}
private void update(boolean remove, ModifyUndoEdit undoEdit) {
int pos = undoEdit.getOffset();
SyntaxSeg.invalidate(doc, pos);
SyntaxSeg.Slot slot = SyntaxSeg.getFreeSlot();
Syntax syntax = doc.getFreeSyntax();
try {
cacheUpdate(remove, undoEdit); // Update line cache
leftUpdateMark = getLeftSyntaxMark(pos - 1); // compute left syntax update mark
int leftUpdatePos = (leftUpdateMark != null) ? leftUpdateMark.getOffset() : 0;
// Add or remove syntax marks that are too close or too far away
updateEvenly();
// Prepare syntax for updating the mark states of the syntax marks
prepareSyntax(slot, syntax, leftUpdateMark, leftUpdatePos, 0);
// Update the syntax marks by mark renderer
suRenderer.slot = slot;
suRenderer.syntax = syntax;
suRenderer.undoEdit = undoEdit;
suRenderer.remove = remove;
marks.render(suRenderer);
} catch (InvalidMarkException e) {
e.printStackTrace();
} catch (BadLocationException e) {
e.printStackTrace();
} finally {
doc.releaseSyntax(syntax);
SyntaxSeg.releaseSlot(slot);
}
}
void initialReadUpdate() { // doesn't need to be synced
docLen = cache.getDocLength();
invalidateCache(); // invalidate the line cache
}
/** This function is called after document insertion to update marks.
* Event parameter contains inserted chars and also how many lines was inserted.
*/
synchronized void insertUpdate(ModifyUndoEdit undoEdit) {
docLen += undoEdit.getLength();
marks.insertUpdate(undoEdit.getOffset(), undoEdit.getLength(), undoEdit.getLFCount());
update(false, undoEdit);
}
/** This function is called after document removal to update marks.
* Event parameter contains removed chars and also how many lines was removed.
*/
synchronized void removeUpdate(ModifyUndoEdit undoEdit) {
docLen -= undoEdit.getLength();
marks.removeUpdate(undoEdit.getOffset(), undoEdit.getLine(), undoEdit.getLength(), undoEdit.getLFCount());
update(true, undoEdit);
}
/** Update marks after insert/removal so that they are
* well evenly distributed. The method is not synchronized on mark renderer
* as all changes must be made under document being write-locked.
*/
private void updateEvenly() {
// Render the marks by mark renderer
marks.render(
new DocMarks.Renderer() {
protected void render() {
int leftMarkIndex = -1;
int leftMarkPos = 0;
if (leftUpdateMark != null) {
leftMarkIndex = getMarkIndex(leftUpdateMark);
try {
leftMarkPos = leftUpdateMark.getOffset();
} catch (InvalidMarkException e) {
e.printStackTrace();
}
}
Mark markArray[] = getMarkArray();
int cnt = getMarkCnt(); // total mark count
int dist = 0; // distance of mark from the current leftMarkPos
Mark m;
int i = leftMarkIndex + 1;
boolean found = false;
// First remove all the marks that are on the same position
for (; i < cnt; i++) {
m = markArray[i];
dist += getRelPos(m);
if (m.getClass() == MarkFactory.SyntaxMark.class) { // syntax mark found
if (m != eolMark) { // eol mark is ignored here
found = true;
break;
}
}
}
if (!found) {
dist = length() - leftMarkPos;
}
// test for too small distance
if (dist < MIN_MARK_DISTANCE && found) {
try {
markArray[i].remove();
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
// test for too large distance
if (dist > MAX_MARK_DISTANCE) {
int insCnt = dist / MARK_DISTANCE;
if (insCnt > 0) {
int startInsPos = leftMarkPos + (dist - (insCnt - 1) * MARK_DISTANCE) / 2;
try {
for (int j = 0; j < insCnt; j++) {
MarkFactory.SyntaxMark newMark = new MarkFactory.SyntaxMark();
insertMark(newMark, startInsPos + j * MARK_DISTANCE);
}
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
}
}
}
);
}
/** Check if the EOL syntax mark is at the right place. If not, move it
* to the end of current line and update it with the right syntax information.
* It uses instance of syntax used in document.
*/
void checkEOLMark(int pos) {
if (docLen == 0) { // empty document is O.K.
return;
}
try {
int eolPos = adjustNextBOL(pos, false);
if (eolMark.getOffset() != eolPos) { // mark at the wrong place
SyntaxSeg.Slot slot = SyntaxSeg.getFreeSlot();
Syntax syntax = doc.getFreeSyntax();
MarkFactory.SyntaxMark mark = getLeftSyntaxMark(eolPos - 1);
try {
moveMark(eolMark, eolPos);
prepareSyntax(slot, syntax, mark, eolPos, 0); // scan up to eolMark ignoring it in search
eolMark.updateStateInfo(syntax);
} finally {
doc.releaseSyntax(syntax);
SyntaxSeg.releaseSlot(slot);
}
}
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
/** Prepare syntax scanner so that it's ready to scan from requested
* position.
* @param slot syntax segment slot to be used
* @param syntax syntax scanner to be used
* @param leftSyntaxMark first syntax mark in the left direction
* from the reqPos, can be obtained by getLeftSyntaxMark()
* @param reqPos position to which the syntax should be prepared
* @param reqLen length that will be scanned by the caller after the syntax
* is prepared. The prepareSyntax() automatically preloads this area
* into the syntax segment slot.
* @return how many characters are in the preScan of the syntax before
* the reqPos
*/
int prepareSyntax(SyntaxSeg.Slot slot, Syntax syntax,
MarkFactory.SyntaxMark leftSyntaxMark, int reqPos, int reqLen)
throws BadLocationException {
// get nearest previous syntax mark
int markPos = 0;
int preScan = 0;
Syntax.StateInfo stateInfo = null;
if (leftSyntaxMark != null) {
stateInfo = leftSyntaxMark.getStateInfo();
preScan = stateInfo.getPreScan();
try {
markPos = leftSyntaxMark.getOffset(); // get position to scan from
} catch (InvalidMarkException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
// load syntax segment
int prepareLen = (reqPos - markPos) + preScan; // length from left mark to reqPos plus preScan
slot.load(doc, markPos - preScan, prepareLen + reqLen);
// load state into syntax scanner - will scan from mark up to reqPos
syntax.load(stateInfo, slot.array,
slot.offset + preScan, prepareLen - preScan, (reqPos >= docLen));
// System.out.println("DocOp.java:536 prepareSyntax(): Loaded state=" + ((leftSyntaxMark != null) ? ((leftSyntaxMark.getStateChain() != null) ? syntax.getStateName(leftSyntaxMark.getStateChain().state) : "nullState") : "nullMark") + ", over array='" + EditorDebug.debugChars(slot.array, slot.offset, slot.count) + "', slot.offset=" + slot.offset + ", slot.count=" + slot.count + ", prepareLen=" + prepareLen + ", preScan=" + preScan + ", reqPos=" + reqPos + ", syntax=" + syntax + ", docLen=" + docLen); // NOI18N
// go through all the tokens till the required position
while (syntax.nextToken() != Syntax.EOT) { }
syntax.setStopOffset(slot.offset + slot.count);
syntax.setLastBuffer(reqPos + reqLen >= docLen);
return syntax.getPreScan();
}
/** Load the line info into lineCache[0]. The line offset must be >= 0.
* @return BOL of loaded line or -1 if line offset is too high
*/
private int cacheLoadLine(int line) {
if (line == 0) { // handle line 0 specially
try {
int eol = cache.find(eolFwdFinder, 0, -1, null);
if (eol == -1) {
eol = docLen;
}
lineCache[0].fill(0, eol, 0);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
return 0;
}
int markLine;
int markPos;
try {
Mark mark = marks.getMarkFromLine(line - 1);
markPos = mark.getOffsetAndLine(tmpLine);
markLine = tmpLine[0];
} catch (InvalidMarkException e) {
return cacheLoadLine(line); // try again
}
try {
beolLineFwdFinder.fwdLines = line - markLine;
markPos = cache.find(beolLineFwdFinder, markPos, -1, null);
int bolPos = beolLineFwdFinder.bolPos;
if (bolPos == -1) { // wrong line
return -1;
}
if (markPos == -1) { // correct eolPos
markPos = docLen;
}
lineCache[0].fill(bolPos, markPos, line);
return bolPos;
} catch (BadLocationException e) {
throw new Error(); // shouldn't happen
}
}
/* Load line when position is known.
* @return line offset of the loaded line
*/
private int cacheLoadLineByPos(int pos) throws BadLocationException {
int markPos;
int markLine;
try {
Mark mark = marks.getLeftMark(pos);
markPos = mark.getOffsetAndLine(tmpLine);
markLine = tmpLine[0];
} catch (InvalidMarkException e) {
return cacheLoadLineByPos(pos); // recall
}
beolPosFwdFinder.tgtPos = pos;
markPos = cache.find(beolPosFwdFinder, markPos, -1, null);
if (markPos == -1) { // correct eolPos
markPos = docLen;
}
int bolPos = beolPosFwdFinder.bolPos;
if (bolPos == -1) { // mark was on the same line with pos
if (pos > 0) {
bolPos = cache.find(bolBwdFinder, pos, 0, null) + 1;
} else {
bolPos = 0;
}
}
int line = markLine + beolPosFwdFinder.line;
lineCache[0].fill(bolPos, markPos, line);
return line;
}
/** Move the entry with some index to be the first in the array */
private void cacheMoveFirst(int ind) {
if (ind == 0) {
return;
}
CacheEntry ent = lineCache[ind];
System.arraycopy(lineCache, 0, lineCache, 1, ind);
lineCache[0] = ent;
}
private void invalidateCache() {
for (int i = 0; i < CACHE_LEN; i++) {
lineCache[i].invalidate();
}
}
/** Update cache after document change. */
private void cacheUpdate(boolean remove, ModifyUndoEdit undoEdit) {
int pos = undoEdit.getOffset();
int len = undoEdit.getLength();
int bolRemovalPos = -1;
int eolRemovalPos = -1;
for (int i = 0; i < CACHE_LEN; i++) {
CacheEntry ent = lineCache[i];
if (ent.line != -1) {
if (!remove) { // insert done
if (pos >= ent.bol) {
if (pos <= ent.eol) { // change inside line
int eolOffset = undoEdit.getFirstLFOffset();
if (eolOffset == -1) { // no LF inside text
ent.eol += len;
} else { // at least one LF inside text
ent.eol = eolOffset;
}
} else { // change after end -> do nothing
}
} else { // pos <= bol -> only move
ent.update(len, undoEdit.getLFCount());
}
} else { // remove done
if (pos + len >= ent.bol) {
if (pos <= ent.eol) { // change before line end
ent.line = undoEdit.getLine();
if (pos + len > ent.eol) { // end of change after EOL
if (eolRemovalPos == -1) {
try {
eolRemovalPos = cache.find(eolFwdFinder, pos, -1, null);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
if (eolRemovalPos == -1) {
eolRemovalPos = docLen;
}
}
ent.eol = eolRemovalPos;
if (pos < ent.bol) { // change before line begin
if (bolRemovalPos == -1) {
try {
bolRemovalPos = cache.find(bolBwdFinder, pos, 0, null) + 1;
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
ent.bol = bolRemovalPos;
}
} else { // end of change before EOL
ent.eol -= len;
if (pos < ent.bol) { // change before line begin
if (bolRemovalPos == -1) {
try {
bolRemovalPos = cache.find(bolBwdFinder, pos, 0, null) + 1;
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
}
ent.bol = bolRemovalPos;
}
}
} else { // change after line end
}
} else { // pos + len <= bol -> only move
ent.update(-len, -undoEdit.getLFCount());
}
}
}
}
}
/** This mark renderer is used to cycle through syntax marks to update them.
* It also helps to compute the mark position in the fast way.
*/
class SyntaxUpdateRenderer extends DocMarks.Renderer {
/** Computed syntax update position */
ModifyUndoEdit undoEdit;
boolean remove;
/** Slot for scanning the document */
SyntaxSeg.Slot slot;
/** Syntax for scanning the document */
Syntax syntax;
/** Get all syntax marks in a given range */
public void render() {
int markCnt = getMarkCnt();
int index = 0;
int pos = 0;
boolean computed = false;
Mark markArray[] = getMarkArray();
int syntaxUpdatePos = -1;
if (leftUpdateMark != null) {
try {
index = getMarkIndex(leftUpdateMark) + 1;
pos = leftUpdateMark.getOffset();
} catch (InvalidMarkException e) {
throw new Error(); // shouldn't happen
}
}
int lastPos = pos; // position of the last syntax mark scanned
int endPos = pos; // ending position of the rescanning
if (!remove) { // insert done
endPos = undoEdit.getOffset() + undoEdit.getLength(); // inserted area that can contain new marks
}
while (index < markCnt) { // possibly till end of mark array
Mark mark = markArray[index++];
pos += getRelPos(mark);
if (mark instanceof MarkFactory.SyntaxMark) {
MarkFactory.SyntaxMark syntaxMark = (MarkFactory.SyntaxMark)mark;
int preScan = syntax.getPreScan();
int loadPos = lastPos - preScan;
int scanLen = pos - loadPos;
if (!slot.isAreaInside(doc, loadPos, scanLen)) {
// load the whole area into syntax segment
int loadSize = Math.min(docLen - loadPos,
Math.max(scanLen, SYNTAX_UPDATE_BATCH_SIZE));
try {
slot.load(doc, loadPos, loadSize);
} catch (BadLocationException e) {
e.printStackTrace();
throw new Error(); // shouldn't happen
}
}
// this load should transfer no data but will adjust scanning offset
try {
slot.load(doc, loadPos, scanLen);
} catch (BadLocationException e) {
e.printStackTrace();
throw new Error(); // shouldn't happen
}
// Relocate scanning for the good offsets
syntax.relocate(slot.array, slot.offset + preScan, scanLen - preScan, (pos == docLen));
// System.out.println("DocOp.java:600 syntax renderer: scan relocated to buffer='" + EditorDebug.debugChars(slot.array, slot.offset + preScan, scanLen - preScan) + "', slot.offset=" + slot.offset + ", scanLen=" + scanLen + ", preScan=" + preScan); // NOI18N
while (syntax.nextToken() != Syntax.EOT) { } // scan till the EOT
if (syntax.compareState(syntaxMark.getStateInfo()) == Syntax.EQUAL_STATE) {
if (syntaxUpdatePos < 0) {
syntaxUpdatePos = pos;
}
if (pos >= endPos && syntaxMark != eolMark) {
break;
}
} else { // state stored in mark is different
syntaxUpdatePos = -1;
syntaxMark.updateStateInfo(syntax);
}
lastPos = pos;
}
}
// Update syntax update position
if (syntaxUpdatePos < 0) {
syntaxUpdatePos = docLen;
}
try {
undoEdit.setSyntaxUpdateOffset(adjustNextBOL(syntaxUpdatePos, true));
} catch (BadLocationException e) {
throw new Error(); // shouldn't happen
}
}
}
/** Cache entry for line and column information caching.
* Currently each entry caches begin and end of line and corresponding
* line number.
*/
private static class CacheEntry {
/** Begin of line pos */
int bol = -1;
/** End of line pos */
int eol = -1;
/** Line offset */
int line = -1;
void fill(int bol, int eol, int line) {
this.bol = bol;
this.eol = eol;
this.line = line;
}
void update(int deltaLen, int deltaLFCount) {
this.bol += deltaLen;
this.eol += deltaLen;
this.line += deltaLFCount;
}
void invalidate() {
bol = eol = line = -1;
}
public String toString() {
return "line=" + line + ", bol=" + bol + ", eol=" + eol; // NOI18N
}
}
/** Dump the line cache */
public String cacheToString() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < CACHE_LEN; i++) {
sb.append("\ncache[" + i + "]: " + lineCache[i].toString()); // NOI18N
}
return sb.toString();
}
public String toString() {
return "statCacheHit=" + statCacheHit // NOI18N
+ ", statCacheMiss=" + statCacheMiss // NOI18N
+ ", Line cache hit ratio=" + (Math.round(1000.0 * statCacheHit / (statCacheHit // NOI18N
+ statCacheMiss)) / 10d)
+ cacheToString();
}
public String markPlanesToString(Class markClasses[], char markChars[]) {
return marks.planesToString(markClasses, markChars);
}
public String infoToString() {
return "\n------------------------------ Statistics ------------------------------\n" // NOI18N
+ "cacheSupport: statCharsRead=" + cacheSupport.statCharsRead // NOI18N
+ ", statCharsWritten=" + cacheSupport.statCharsWritten // NOI18N
+ "\nCache: " + cache // NOI18N
+ "\nMarks: " + marks // NOI18N
+ "\nDocOp: " + this; // NOI18N
}
/**
* UnoableEdit created for inserts and removals.
* The <tt>remove</tt> flag determines whether it's insert or removal
*/
class ModifyUndoEdit extends AbstractUndoableEdit {
/** Whether removal was done instead of insertion */
boolean remove;
/** Offset where the characters was inserted */
private int offset;
/** The inserted characters. If the string was used
* for the insertion this will be lazily initialized.
*/
private char[] chars;
/** The inserted string. If the character buffer was used
* for the insertion this will be lazily initialized.
*/
private String text;
/** The number of the '\n' (line-feed) characters contained
* in the inserted text. It's lazily initialized.
*/
private int lfCount = -1;
/** Line offset of the insert/removal */
private int line;
/** Offset of the end of the syntax updating */
private int syntaxUpdateOffset;
ModifyUndoEdit(boolean remove, int offset, char[] chars) {
this.remove = remove;
this.offset = offset;
this.chars = chars;
try {
this.line = DocOp.this.getLine(offset);
} catch (BadLocationException e) {
}
}
ModifyUndoEdit(boolean remove, int offset, String text) {
this.remove = remove;
this.offset = offset;
this.text = text;
try {
this.line = DocOp.this.getLine(offset);
} catch (BadLocationException e) {
}
}
boolean isInsert() {
return !remove;
}
boolean isRemove() {
return remove;
}
final int getOffset() {
return offset;
}
/** Get the length of the inserted/removed text */
int getLength() {
return (chars != null) ? chars.length : text.length();
}
/** Get the inserted text. If the character buffer was used
* for the insertion then the appropriate variable will be lazily initialized first.
*/
String getText() {
if (text == null) {
text = new String(chars);
}
return text;
}
/** The inserted characters. If the string was used
* for the insertion then the appropriate variable will be lazily initialized first.
*/
char[] getChars() {
if (chars == null) {
chars = text.toCharArray();
}
return chars;
}
/** Whether the text is valid instead of chars. This method helps
* to avoid the possible conversion from chars to string.
*/
boolean isTextValid() {
return (text != null);
}
/** Get the number of the '\n' (line-feed) characters contained
* in the inserted text. It's lazily initialized if necessary.
*/
int getLFCount() {
if (lfCount == -1) {
if (chars != null) { // chars valid valid
lfCount = Analyzer.getLFCount(chars);
} else { // string valid
lfCount = Analyzer.getLFCount(text);
}
}
return lfCount;
}
/** Get the document offset of the first LF contained
* in the inserted/removed text or -1 for no LFs.
* This value is not cached.
*/
int getFirstLFOffset() {
if (getLFCount() <= 0) {
return -1;
}
int flfOffset;
if (chars != null) { // chars valid valid
flfOffset = Analyzer.getFirstLFOffset(chars);
} else { // string valid
flfOffset = Analyzer.getFirstLFOffset(text);
}
if (flfOffset >= 0) {
flfOffset += offset; // shift by the insert/update offset
}
return flfOffset;
}
int getLine() {
return line;
}
int getSyntaxUpdateOffset() {
return syntaxUpdateOffset;
}
void setSyntaxUpdateOffset(int syntaxUpdateOffset) {
this.syntaxUpdateOffset = syntaxUpdateOffset;
}
public void undo() throws CannotUndoException {
super.undo();
try {
if (remove) {
insertEdit(this);
} else { // insertion
removeEdit(this);
}
} catch (BadLocationException bl) {
throw new CannotUndoException();
}
}
public void redo() throws CannotRedoException {
super.redo();
try {
if (remove) {
removeEdit(this);
} else { // insertion
insertEdit(this);
}
} catch (BadLocationException bl) {
throw new CannotRedoException();
}
}
}
}
/*
* $Log:
* 8 Gandalf-post-FCS1.7 4/3/00 Miloslav Metelka undo update
* 7 Gandalf-post-FCS1.6 3/16/00 Miloslav Metelka reverted back
* 6 Gandalf-post-FCS1.5 3/16/00 Miloslav Metelka reverted back to
* structural for Jaga only
* 5 Gandalf-post-FCS1.4 3/16/00 Miloslav Metelka reverted back for
* postFCS
* 4 Gandalf-post-FCS1.3 3/16/00 Miloslav Metelka reverted back to
* structural change in Jaga view (propagated from postFCS)
* 3 Gandalf-post-FCS1.2 3/15/00 Miloslav Metelka reverted previous
* version - ST error?
* 2 Gandalf-post-FCS1.1 3/15/00 Miloslav Metelka Structural change
* 1 Gandalf-post-FCS1.0 3/8/00 Miloslav Metelka
* $
*/